package com.jay.android.obd;

import com.jay.android.ble.BluetoothLeService;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.net.NetworkInfo.State;
import android.os.Handler;
import android.os.IBinder;

@SuppressLint("NewApi")
public class OBDDevices {
	private static OBDDevices instance = null;
	static {
		instance = new OBDDevices();
	}

	private OBDDevices() {

	}

	public static OBDDevices getInstance() {
		return instance;
	}

	private Context mContext;
	private BluetoothLeService mBluetoothLeService;
	private BluetoothAdapter mBluetoothAdapter;
	private String bleAddress = null;
	private boolean isBleConnected = false;
	private Handler mBleHandler;
	private static final long SCAN_PERIOD = 10000;
	private final String BLE_HEAD = "CPLB";
	private Handler mHandler;

	public static final int OBD_CONNECT_STATE = 800;
	public static final int OBD_CONNECT_STATE_CONNECTED = 801;
	public static final int OBD_CONNECT_STATE_CONNECTING = 802;
	public static final int OBD_CONNECT_STATE_DISCONNECTED = 803;
	public static final int OBD_CONNECT_STATE_TIMEOUT = 804;

	public static final int OBD_MESSAGE_COMMING = 900;
	public static final int OBD_MESSAGE_SPEED = 901;

	public static final int CMD_STATE_START = 1000;
	public static final int CMD_STATE_TYPE = 1001;
	public static final int CMD_STATE_LEN = 1002;
	public static final int CMD_STATE_DATA = 1003;
	public static final int CMD_STATE_COMPLETE = 1004;

	private int cmdState;
	private int cmdType;
	private int cmdLen;
	private int[] cmdData;
	private int[] atCmd;
	private int atLen;

	private final ServiceConnection mBleServiceConnection = new ServiceConnection() {

		@Override
		public void onServiceConnected(ComponentName componentName,
				IBinder service) {
			mBluetoothLeService = ((BluetoothLeService.LocalBinder) service)
					.getService();

			if (!mBluetoothLeService.initialize()) {
				System.out.println("Unable to initialize Bluetooth");
			}
			// Automatically connects to the device upon successful start-up
			// initialization.
			mBluetoothLeService.connect(bleAddress);
		}

		@Override
		public void onServiceDisconnected(ComponentName componentName) {
			mBluetoothLeService = null;
		}
	};

	public static int[] barr2iarr(byte[] barr) {
		int len = barr.length;
		int[] iarr = new int[len];
		for (int i = 0; i < len; i++) {
			if (barr[i] > -1) {
				iarr[i] = barr[i];
			} else {
				iarr[i] = barr[i] + 256;
			}
		}
		return iarr;
	}

	private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver() {
		@Override
		public void onReceive(Context context, Intent intent) {
			final String action = intent.getAction();
			if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) {
				isBleConnected = true;
				mHandler.obtainMessage(OBD_CONNECT_STATE,
						OBD_CONNECT_STATE_CONNECTED, -1).sendToTarget();
			} else if (BluetoothLeService.ACTION_GATT_DISCONNECTED
					.equals(action)) {
				isBleConnected = false;
				mHandler.obtainMessage(OBD_CONNECT_STATE,
						OBD_CONNECT_STATE_DISCONNECTED, -1).sendToTarget();
			} else if (BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED
					.equals(action)) {
			} else if (BluetoothLeService.ACTION_DEVICE_FIND.equals(action)) {
				// send connect can bus
				active_obd();
			} else if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action)) {
				// process data
				byte data[] = intent
						.getByteArrayExtra(BluetoothLeService.EXTRA_DATA);
				int[] idata = barr2iarr(data);
				for (int i = 0; i < idata.length; i++) {
					if (handleCmd(idata[i])) {
						processCmd();
					}
				}
				// int cmdType = idata[3];
				// int len = idata[4] * 256 + idata[5];
				// int[] d = new int[len];
				// if (len != (idata.length - 7))
				// return;
				//
				// for (int i = 0; i < len; i++) {
				// d[i] = idata[6 + i];
				// }
				//
				// switch (cmdType) {
				// case OBDCmd.CMD_GET_SPEED:
				// int speed = d[0] * 256 + d[1];
				// mHandler.obtainMessage(OBD_MESSAGE_COMMING,
				// OBD_MESSAGE_SPEED, speed).sendToTarget();
				// break;
				//
				// default:
				// break;
				// }

			}
		}
	};

	private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {

		@Override
		public void onLeScan(final BluetoothDevice device, int rssi,
				byte[] scanRecord) {
			((Activity) mContext).runOnUiThread(new Runnable() {
				@Override
				public void run() {
					final String deviceName = device.getName();
					if (deviceName != null && deviceName.length() > 0) {

						if (deviceName.startsWith(BLE_HEAD)) {
							if (!isBleConnected) {
								bleAddress = device.getAddress();
								if (mBluetoothLeService != null) {
									mBluetoothLeService.connect(bleAddress);
								}
							}
						}
					}
				}
			});
		}
	};

	private void scanLeDevice(final boolean enable) {

		if (enable) {
			// Stops scanning after a pre-defined scan period.
			mBleHandler.postDelayed(new Runnable() {
				@Override
				public void run() {
					if (!isBleConnected) {
						mBluetoothAdapter.stopLeScan(mLeScanCallback);
						mHandler.obtainMessage(OBD_CONNECT_STATE,
								OBD_CONNECT_STATE_TIMEOUT, -1).sendToTarget();
					}
				}
			}, SCAN_PERIOD);

			mBluetoothAdapter.startLeScan(mLeScanCallback);
		} else {
			mBluetoothAdapter.stopLeScan(mLeScanCallback);
		}
	}

	private static IntentFilter makeGattUpdateIntentFilter() {
		final IntentFilter intentFilter = new IntentFilter();
		intentFilter.addAction(BluetoothLeService.ACTION_GATT_CONNECTED);
		intentFilter.addAction(BluetoothLeService.ACTION_GATT_DISCONNECTED);
		intentFilter
				.addAction(BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED);
		intentFilter.addAction(BluetoothLeService.ACTION_DATA_AVAILABLE);
		intentFilter.addAction(BluetoothLeService.ACTION_DEVICE_FIND);
		intentFilter.addAction(BluetoothLeService.EXTRA_DATA);

		return intentFilter;
	}

	private void sendCmd(byte cmd) {
		byte bytes[] = { 0x55, 0x55, 0x01, 0x00, 0x00, 0x00, 0x00 };
		bytes[3] = cmd;
		byte sum = 0x00;
		for (int i = 0; i < bytes.length - 1; i++) {
			sum += bytes[i];
		}
		bytes[6] = sum;
		mBluetoothLeService.send(bytes);
	}

	private void active_obd() {
		sendCmd((byte) OBDCmd.CMD_ACTIVE_OBD);
	}

	private void resetCmd() {
		cmdType = 0;
		cmdLen = 0;
		for (int i = 0; i < cmdData.length; i++) {
			cmdData[i] = 0;
		}
		for (int i = 0; i < atCmd.length; i++) {
			atCmd[i] = 0;
		}
		atLen = 0;
		cmdState = CMD_STATE_START;
	}

	private boolean handleCmd(int d) {
		boolean result = false;
		atCmd[atLen++] = d;

		switch (cmdState) {
		case CMD_STATE_START:
			if (atLen == 1) {
				if (atCmd[0] != 0x55) {
					resetCmd();
				}
			} else if (atLen == 2) {
				if (atCmd[0] != 0x55 || (atCmd[1] != 0x55)) {
					resetCmd();
				}
				if (atCmd[0] == 0x55 && (atCmd[1] == 0x55)) {
					cmdState = CMD_STATE_TYPE;
				}
			}
			break;

		case CMD_STATE_TYPE:
			if (atLen == 4) {
				cmdType = atCmd[3];
				cmdState = CMD_STATE_LEN;
			}
			break;

		case CMD_STATE_LEN:
			if (atLen == 6) {
				cmdLen = atCmd[4] * 256 + atCmd[5];
				if (cmdLen == 0) {
					cmdState = CMD_STATE_COMPLETE;
				} else {
					cmdState = CMD_STATE_DATA;
				}
			}
			break;

		case CMD_STATE_DATA:
			int len = cmdLen;
			if (atLen == (len + 6)) {
				for (int i = 0; i < len; i++) {
					cmdData[i] = atCmd[6 + i];
				}
				cmdState = CMD_STATE_COMPLETE;
			}
			break;

		case CMD_STATE_COMPLETE:
			// TODO: Checksum
			result = true;
			break;

		default:
			break;
		}
		return result;
	}

	private void processCmd() {
		switch (cmdType) {
		case OBDCmd.CMD_GET_SPEED:
			int speed = cmdData[0] * 256 + cmdData[1];
			mHandler.obtainMessage(OBD_MESSAGE_COMMING, OBD_MESSAGE_SPEED,
					speed).sendToTarget();
			break;

		default:
			break;
		}
	}

	public void init(Context context) {
		mContext = context;
		mBleHandler = new Handler();
		final BluetoothManager bluetoothManager = (BluetoothManager) mContext
				.getSystemService(Context.BLUETOOTH_SERVICE);
		mBluetoothAdapter = bluetoothManager.getAdapter();
		Intent gattServiceIntentFilter = new Intent(mContext,
				BluetoothLeService.class);
		mContext.bindService(gattServiceIntentFilter, mBleServiceConnection,
				Context.BIND_AUTO_CREATE);
		cmdData = new int[100];
		atCmd = new int[100];
		resetCmd();
	}

	public void register(Handler handler) {
		mContext.registerReceiver(mGattUpdateReceiver,
				makeGattUpdateIntentFilter());
		mHandler = handler;
	}

	public void unregister() {
		mContext.unregisterReceiver(mGattUpdateReceiver);
	}

	public void release() {
		mBluetoothAdapter.stopLeScan(mLeScanCallback);
		mBluetoothLeService.disconnect();
		mContext.unbindService(mBleServiceConnection);
	}

	public void connect() {
		if (isBleConnected) {
			mHandler.obtainMessage(OBD_CONNECT_STATE,
					OBD_CONNECT_STATE_CONNECTED, -1).sendToTarget();
			return;
		}
		isBleConnected = false;
		scanLeDevice(true);
		mHandler.obtainMessage(OBD_CONNECT_STATE, OBD_CONNECT_STATE_CONNECTING,
				-1).sendToTarget();
		if (!mBluetoothLeService.initialize()) {
			((Activity) mContext).finish();
		}
		mBluetoothLeService.connect(bleAddress);
	}

	public void getPayload() {
		sendCmd((byte) OBDCmd.CMD_GET_PAYLOAD);
	}

	public void getSpeed() {
		sendCmd((byte) OBDCmd.CMD_GET_SPEED);
	}

	public void getRpm() {
		sendCmd((byte) OBDCmd.CMD_GET_RPM);
	}

	public void getEct() {
		sendCmd((byte) OBDCmd.CMD_GET_ECT);
	}
}
